//
//  PXLPhoneHelper.mm
//  PXL
//
//  Created by Rachel Blackman on 8/9/07.
//  Copyright 2007 Riverdark Studios. All rights reserved.
//

#import "PXLPhoneHelper.h"

#include "PhoneInteraction/PhoneInteraction.h"
#include <unistd.h>
#include <utime.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

#include "unzip.h"
#include "../PXLdaemon/config.h"

static PhoneInteraction *_iPhone = NULL;
static PXLPhoneHelper *s_pxlPhoneHelper = nil;

@interface PXLPhoneHelper (Private)
- (void) refreshState;
- (void) refreshPackages;
- (void) watchForPackageUpdate:(id) dummy;
- (void) setConnected:(BOOL)connected;
- (void) jailbreakBeginning;
- (void) jailbreakWaiting;
- (void) installJailbreakSucceeded;
- (void) installJailbreakFailed;
- (void) installJailbreakCancelled;
- (int) installPxlJailbroken;
- (int) installPxlJailed;
@end

@interface PXLPhoneHelper (InformalDelegate)
- (void) phoneDidConnect:(PXLPhoneHelper *)phone;
- (void) phoneDidDisconnect:(PXLPhoneHelper *)phone;
- (void) phone:(PXLPhoneHelper *)phone stateDidChangeFrom:(int)oldState to:(int)newState;

- (void) phoneDidRefreshPackages:(PXLPhoneHelper *)phone;
- (void) phone:(PXLPhoneHelper *)phone willInstallPackage:(NSString *)packageFile;
- (void) phone:(PXLPhoneHelper *)phone didInstallPackage:(NSString *)packageFile;
- (void) phone:(PXLPhoneHelper *)phone willRemovePackage:(NSString *)packageId;
- (void) phone:(PXLPhoneHelper *)phone didRemovePackage:(NSString *)packageId;

- (void) phoneWillUpdateDaemon:(PXLPhoneHelper *)phone;
- (void) phoneDidUpdateDaemon:(PXLPhoneHelper *)phone;

- (void) phoneIsUnpackingFirmware:(PXLPhoneHelper *)phone percentDone:(int) percentDone;
- (void) phoneWillJailbreak:(PXLPhoneHelper *)phone;
- (void) phoneIsJailbreaking:(PXLPhoneHelper *)phone;
- (void) phoneDidJailbreak:(PXLPhoneHelper *)phone;
- (void) phoneCancelledJailbreak:(PXLPhoneHelper *)phone;
- (void) phoneCouldNotJailbreak:(PXLPhoneHelper *)phone;

- (void) phoneCouldNotInstallPxl:(PXLPhoneHelper *)phone forReason:(NSString *)reason;

- (void) phoneWillReset:(PXLPhoneHelper *)phone forReason:(NSString *)reason;

@end

void statusCallback(const char *msg, bool waiting)
{
    // Do nothing, for now.
}

void phoneInteractionCallback(int type, const char *msg)
{

	if (s_pxlPhoneHelper) {

		switch (type) {
			case NOTIFY_CONNECTED:
				[s_pxlPhoneHelper setConnected:true];
				break;
			case NOTIFY_DISCONNECTED:
				[s_pxlPhoneHelper setConnected:false];
				break;
			case NOTIFY_INITIALIZATION_FAILED:
			case NOTIFY_CONNECTION_FAILED:

				break;
			case NOTIFY_JAILBREAK_SUCCESS:
                [s_pxlPhoneHelper installJailbreakSucceeded];
				break;
			case NOTIFY_PUTSERVICES_FAILED:
			case NOTIFY_PUTFSTAB_FAILED:
			case NOTIFY_JAILBREAK_FAILED:
			case NOTIFY_JAILBREAK_RECOVERY_WAIT:
                [s_pxlPhoneHelper jailbreakBeginning];
				break;
			case NOTIFY_JAILBREAK_RECOVERY_CONNECTED:
                [s_pxlPhoneHelper jailbreakWaiting];
				break;
			case NOTIFY_JAILBREAK_RECOVERY_DISCONNECTED:

				break;
			case NOTIFY_JAILBREAK_RESTORE_CONNECTED:

				break;
			case NOTIFY_JAILBREAK_RESTORE_DISCONNECTED:

				break;
			case NOTIFY_JAILBREAK_CANCEL:
                [s_pxlPhoneHelper installJailbreakCancelled];
				break;
			case NOTIFY_CONNECTION_SUCCESS:
			case NOTIFY_INITIALIZATION_SUCCESS:
			case NOTIFY_PUTFSTAB_SUCCESS:
			case NOTIFY_PUTSERVICES_SUCCESS:
			default:
				break;
		}

	}

}

#pragma mark Helper Functions

void uz_change_file_date(const char *filename, uLong dosdate,tm_unz tmu_date)
{
  struct utimbuf ut;
  struct tm newdate;
  newdate.tm_sec = tmu_date.tm_sec;
  newdate.tm_min=tmu_date.tm_min;
  newdate.tm_hour=tmu_date.tm_hour;
  newdate.tm_mday=tmu_date.tm_mday;
  newdate.tm_mon=tmu_date.tm_mon;
  if (tmu_date.tm_year > 1900)
      newdate.tm_year=tmu_date.tm_year - 1900;
  else
      newdate.tm_year=tmu_date.tm_year ;
  newdate.tm_isdst=-1;

  ut.actime=ut.modtime=mktime(&newdate);
  utime(filename,&ut);
}

int uz_makedir (char *newdir)
{
  char *buffer ;
  char *p;
  int  len = (int)strlen(newdir);

  if (len <= 0)
    return 0;

  buffer = (char*)malloc(len+1);
  strcpy(buffer,newdir);

  if (buffer[len-1] == '/') {
    buffer[len-1] = '\0';
  }
  if (mkdir(buffer,S_IRWXU | S_IRWXG | S_IRWXO) == 0)
    {
      free(buffer);
      return 1;
    }

  p = buffer+1;
  while (1)
    {
      char hold;

      while(*p && *p != '\\' && *p != '/')
        p++;
      hold = *p;
      *p = 0;
      if ((mkdir(buffer,S_IRWXU | S_IRWXG | S_IRWXO) != 0))
        {
          free(buffer);
          return 0;
        }
      if (hold == 0)
        break;
      *p++ = hold;
    }
  free(buffer);
  return 1;
}

int uz_extract_currentfile(unzFile uf,const char *password)
{
    char filename_inzip[256];
    char* filename_withoutpath;
    char* p;
    int err=UNZ_OK;
    FILE *fout=NULL;
    void* buf;
    uInt size_buf;

    unz_file_info file_info;
    uLong ratio=0;
    err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);

    if (err!=UNZ_OK)
    {
        return err;
    }

    size_buf = WRITEBUFFERSIZE;
    buf = (void*)malloc(size_buf);
    if (buf==NULL)
    {
        return UNZ_INTERNALERROR;
    }

    p = filename_withoutpath = filename_inzip;
    while ((*p) != '\0')
    {
        if (((*p)=='/') || ((*p)=='\\'))
            filename_withoutpath = p+1;
        p++;
    }

    if ((*filename_withoutpath)=='\0')
    {
        mkdir(filename_inzip,S_IRWXU | S_IRWXG | S_IRWXO);
    }
    else
    {
        const char* write_filename;
        int skip=0;

        write_filename = filename_inzip;

        err = unzOpenCurrentFilePassword(uf,password);
        if (err!=UNZ_OK)
        {
            return err;
        }
        
        if ((skip==0) && (err==UNZ_OK))
        {
            fout=fopen(write_filename,"wb");

            /* some zipfile don't contain directory alone before file */
            if ((fout==NULL) &&
                                (filename_withoutpath!=(char*)filename_inzip))
            {
                char c=*(filename_withoutpath-1);
                *(filename_withoutpath-1)='\0';
                uz_makedir((char *)write_filename);
                *(filename_withoutpath-1)=c;
                fout=fopen(write_filename,"wb");
            }
        }

        if (fout!=NULL)
        {
            mode_t filemode;
            do
            {
                err = unzReadCurrentFile(uf,buf,size_buf);
                if (err<0)
                {
                    break;
                }
                if (err>0)
                    if (fwrite(buf,err,1,fout)!=1)
                    {
                        err=UNZ_ERRNO;
                        break;
                    }
            }
            while (err>0);
            if (fout)
                    fclose(fout);

            filemode = 0xFFFF & (file_info.external_fa >> 16L);

            chmod(write_filename,filemode);
                    
            if (err==0)
                uz_change_file_date(write_filename,file_info.dosDate,
                                 file_info.tmu_date);
        }

        if (err==UNZ_OK)
        {
            err = unzCloseCurrentFile (uf);
        }
        else
            unzCloseCurrentFile(uf); /* don't lose the error */
    }

    free(buf);
    return err;
}

int exists_file(const char *filename)
{
    struct stat st;

    if(stat(filename, &st) == 0)
        return 1;
	
    return 0;
}

int exists_dir(const char *filename)
{
    struct stat st;

    if(stat(filename, &st) != 0)
        return 0;
	
    if(!S_ISDIR(st.st_mode))
        return 0;

    return 1;
}

void delete_dir(const char *path)
{
    DIR *workdir;
    struct dirent *dp;
    struct stat tstat;
    char *fullpath;
    int len;
    
    workdir = opendir(path);
    if (workdir) {
        while ((dp = readdir(workdir)) != NULL) {
            if (dp && dp->d_name && strcmp(dp->d_name,".") && strcmp(dp->d_name,"..")) {
               len = strlen(path) + strlen(dp->d_name) + 3;
               fullpath = (char *)malloc(len);
               memset(fullpath,0,len);
               snprintf(fullpath,len - 1,"%s/%s", path, dp->d_name);
    
               if (exists_dir(fullpath)) {
                   delete_dir(fullpath);
                   rmdir(fullpath);
               }
               else if (exists_file(fullpath)) {
                   unlink(fullpath);
               }
               free(fullpath);
            }
        }
        closedir(workdir);
        rmdir(path);
    }
}

@implementation PXLPhoneHelper

#pragma mark Initialization and Teardown

+ (PXLPhoneHelper *) sharedPhoneHelper
{
    if (!s_pxlPhoneHelper) {
        s_pxlPhoneHelper = [[PXLPhoneHelper alloc] init];
    }
    
    return s_pxlPhoneHelper;
}

- (id) init
{
    self = [super init];
    if (self) {
        _pxlPackages = [[NSMutableDictionary alloc] init];
        _pxlPackageIDs = [[NSMutableArray alloc] init];
        _phoneFirmwareInfo = nil;
        _pxlDelegate = nil;
        _pxlLastUpdate = nil;
        _phoneState = PhoneStateUnknown;
        
        _phoneName = nil;
        _phoneNumber = nil;
        _phoneSerial = nil;
        _phoneFirmwareBuild = nil;
        _phoneFirmwareRestoreURL = nil;
        _phoneFirmwareDisplay = nil;
        if (!_iPhone)
            _iPhone = PhoneInteraction::getInstance(statusCallback, phoneInteractionCallback);        
    }
    return self;
}

- (void)dealloc
{
    [_phoneFirmwareInfo release];
    [_phoneFirmwareDisplay release];
    [_phoneFirmwareRestoreURL release];
    [_pxlPackages release];
    [_pxlPackageIDs release];
    [_pxlLastUpdate release];
	[super dealloc];
}

#pragma mark State and Cache

- (void) setDelegate:(id) delegate
{
    _pxlDelegate = delegate;
}

- (void) retrieveFirmwareVersions
{
    if (_phoneFirmwareDisplay) {
        [_phoneFirmwareDisplay release];
        _phoneFirmwareDisplay = nil;
    }

    if (_phoneFirmwareRestoreURL) {
        [_phoneFirmwareRestoreURL release];
        _phoneFirmwareRestoreURL = nil;
    }
    NSString *firmwareBuild = [self phoneFirmwareBuild];        
        
    if ([firmwareBuild isEqualToString:@"1A543a"]) {
        _phoneFirmwareDisplay = [[NSString alloc] initWithString:@"1.0"];
        _phoneFirmwareRestoreURL = [[NSString alloc] initWithString:@"http://appldnld.apple.com.edgesuite.net/content.info.apple.com/iPhone/061-3538.20070629.B7vXa/iPhone1,1_1.0_1A543a_Restore.ipsw"];
    }
    else if ([firmwareBuild isEqualToString:@"1C25"]) {
        _phoneFirmwareDisplay = [[NSString alloc] initWithString:@"1.0.1"];
        _phoneFirmwareRestoreURL = [[NSString alloc] initWithString:@"http://appldnld.apple.com.edgesuite.net/content.info.apple.com/iPhone/061-3614.20070731.Nt6Y7/iPhone1,1_1.0.1_1C25_Restore.ipsw"];
    }
    else if ([firmwareBuild isEqualToString:@"1C28"]) {
        _phoneFirmwareDisplay = [[NSString alloc] initWithString:@"1.0.2"];
        _phoneFirmwareRestoreURL = [[NSString alloc] initWithString:@"http://appldnld.apple.com.edgesuite.net/content.info.apple.com/iPhone/061-3823.20070821.vormd/iPhone1,1_1.0.2_1C28_Restore.ipsw"];
    }

    if (_phoneFirmwareDisplay && _phoneFirmwareRestoreURL)
        return;
        
    if (!_phoneFirmwareInfo) {
        NSURL *url = [NSURL URLWithString:@"http://phobos.apple.com/versions"];
        _phoneFirmwareInfo = [[NSDictionary alloc] initWithContentsOfURL:url];
    }
    
    if (_phoneFirmwareInfo) {
        NSDictionary *tempDict = [_phoneFirmwareInfo objectForKey:@"MobileDeviceSoftwareVersions"];
        
        if (!tempDict)
            return;
        
        tempDict = [tempDict objectForKey:@"iPhone1,1"];
        if (!tempDict)
            return;

        tempDict = [tempDict objectForKey:firmwareBuild];            
        if (!tempDict)
            return;
        
        tempDict = [tempDict objectForKey:@"Restore"];
        if (!tempDict)
            return;

        _phoneFirmwareDisplay = [[tempDict objectForKey:@"ProductVersion"] copy];
        _phoneFirmwareRestoreURL = [[tempDict objectForKey:@"FirmwareURL"] copy];
    }

 }

- (void) refreshState
{
    int oldState = _phoneState;

    if (_phoneConnected) {
        [self retrieveFirmwareVersions];
        
        if (_iPhone->fileExists(_iPhone->isPhoneJailbroken() ? "/private/var/root/Media/PXL" : "/PXL")) {
            _phoneState = PhoneStatePxlEnabled;
            [self refreshPackages];
            [NSThread detachNewThreadSelector:@selector(watchForPackageUpdate:) toTarget:self withObject:nil];
        }
        else {
            if (_iPhone->isPhoneJailbroken()) {                
                _phoneState = PhoneStateJailbroken;
            }
            else {
                _phoneState = PhoneStateJailed;
            }
        }    
    }
    else {
        _phoneState = PhoneStateUnknown;
    }
        
    if (_phoneState != oldState) {
        if ([_pxlDelegate respondsToSelector:@selector(phone:stateDidChangeFrom:to:)]) {
            [_pxlDelegate phone:self stateDidChangeFrom:oldState to:_phoneState];
        }
    }
}

- (int) phoneState
{
    return _phoneState;
}

- (BOOL) isConnected
{
    return _iPhone->isConnected();
}

- (void) setConnected:(BOOL) connected
{
    if (!connected) {
        [_phoneFirmwareDisplay release];
        [_phoneFirmwareRestoreURL release];
        _phoneFirmwareDisplay = nil;
        _phoneFirmwareRestoreURL = nil;
        
        [_phoneName release];
        [_phoneNumber release];
        [_phoneSerial release];
        [_phoneFirmwareBuild release];
        
        _phoneName = _phoneNumber = _phoneSerial = _phoneFirmwareBuild = nil;
        
        [_pxlPackages removeAllObjects];
    }

    if (connected && [_pxlDelegate respondsToSelector:@selector(phoneDidConnect:)]) {
        [_pxlDelegate phoneDidConnect:self];
    }
    else if (!connected && [_pxlDelegate respondsToSelector:@selector(phoneDidDisconnect:)]) {
        [_pxlDelegate phoneDidDisconnect:self];
    }

    _phoneConnected = connected;
    [self refreshState];
}

#pragma mark Device Information

- (NSString *)phoneName
{
    if (!_phoneName && _iPhone->isConnected()) {
        _phoneName = [(NSString *)_iPhone->getValue((CFStringRef)@"DeviceName") copy];
    }

    return _phoneName;
}

- (NSString *)phoneNumber
{
    if (!_phoneNumber && _iPhone->isConnected()) {
        NSString *result = (NSString *)_iPhone->getValue((CFStringRef)@"PhoneNumber");

        if (result && ([result length] == 11)) {
            NSString *final = [NSString stringWithFormat:@"%@ (%@) %@-%@",
                [result substringWithRange:NSMakeRange(0,1)],
                [result substringWithRange:NSMakeRange(1,3)],
                [result substringWithRange:NSMakeRange(4,3)],
                [result substringWithRange:NSMakeRange(7,4)]];
            
            result = final;
        }
        _phoneNumber = [result copy];
    }
    
    return _phoneNumber;
}

- (NSString *) phoneSerial
{
    if (!_phoneSerial && _iPhone->isConnected()) {
        _phoneSerial = [(NSString *)_iPhone->getValue((CFStringRef)@"SerialNumber") copy];
    }

    return _phoneSerial;
}

- (NSString *) phoneFirmwareBuild
{
    if (!_phoneFirmwareBuild) {
        _phoneFirmwareBuild = [(NSString *)_iPhone->getValue((CFStringRef)@"BuildVersion") copy];
    }

    return _phoneFirmwareBuild;
}

- (NSString *) phoneFirmwareDisplay
{
    if (!_phoneFirmwareDisplay) {
        [self retrieveFirmwareVersions];
    }
    return _phoneFirmwareDisplay;
}

- (NSString *) phoneFirmwareRestoreURL
{
    return _phoneFirmwareRestoreURL;
}

- (int) phonePxlRevision
{
    return _pxlVersion;
}

#pragma mark Package Management


- (BOOL) createDirectories:(NSString *) path
{
    NSArray *pathComponents = [[path stringByExpandingTildeInPath] pathComponents];
    
    NSString *currentPath = @"";
    NSFileManager *fileman = [NSFileManager defaultManager];
    BOOL isDirectory;
    
    NSEnumerator *pathEnum = [pathComponents objectEnumerator];
    NSString *pathFragment = @"/";
    
    do {
        currentPath = [currentPath stringByAppendingPathComponent:pathFragment];
        if ([fileman fileExistsAtPath:currentPath isDirectory:&isDirectory]) {
            if (!isDirectory)
                return NO;
        }
        else {
            if (![fileman createDirectoryAtPath:currentPath attributes:nil])
                return NO;
        }
    } while (pathFragment = [pathEnum nextObject]);

    return YES;
}

- (void) parseDirectoryFiles:(NSString *)source forDest:(NSString *)dest intoArray:(NSMutableArray *)fileList
{
    BOOL directory;
    if ([[NSFileManager defaultManager] fileExistsAtPath:source isDirectory:&directory]) {
        if (![dest isEqualToString:@"/"])
            [fileList addObject:dest];
        if (directory) {            
            DIR *workdir;
            struct dirent *dp;
            struct stat tstat;
            char *fullpath;
            int len;
            
            workdir = opendir([source cString]);
            if (workdir) {
                while ((dp = readdir(workdir)) != NULL) {
                    if (dp && dp->d_name && strcmp(dp->d_name,".") && strcmp(dp->d_name,"..")) {
                        NSString *newSrc = [NSString stringWithFormat:@"%@/%s", source, dp->d_name];
                        NSString *newDest = [dest stringByAppendingPathComponent:[NSString stringWithCString:dp->d_name]];
                        
                        [self parseDirectoryFiles:newSrc forDest:newDest intoArray:fileList];
                    }
                }
            }            
        }
    }
}

- (NSDictionary *) parsePxlPackage:(NSString *)package
{
    NSString *tempDirectory = [NSString stringWithFormat:@"%@/breezy/", NSTemporaryDirectory()];
    
    [self createDirectories:tempDirectory];
    [[NSFileManager defaultManager] changeCurrentDirectoryPath:tempDirectory];
    
    unzFile uf;
    uLong i;
    unz_global_info gi;
    int err;

    uf = unzOpen([package cString]);
    if (!uf) {
        return nil;
    }

    err = unzGetGlobalInfo (uf,&gi);
    if (err!=UNZ_OK) {
        unzClose(uf);
        return nil;
    }

    for (i=0;i<gi.number_entry;i++)
    {
        if (uz_extract_currentfile(uf,NULL) != UNZ_OK)
            break;
            
        if ((i+1)<gi.number_entry)
        {
            err = unzGoToNextFile(uf);
            if (err!=UNZ_OK)
            {
                unzClose(uf);
                delete_dir([tempDirectory cString]);    
                return nil;
            }
        }
    }
    unzClose(uf);    
    
    NSString *pxlManifest = [tempDirectory stringByAppendingPathComponent:@"PxlPkg.plist"];
    NSMutableDictionary *pxlDict = [[NSDictionary dictionaryWithContentsOfFile:pxlManifest] mutableCopy];
    
    [pxlDict setObject:package forKey:@"RDPxlPackageBreezyPath"];
    
    if ([pxlDict objectForKey:@"RDPxlPackageIcon"]) {
        NSString *iconFile = [tempDirectory stringByAppendingString:[pxlDict objectForKey:@"RDPxlPackageIcon"]];
        
        if ([[NSFileManager defaultManager] fileExistsAtPath:iconFile]) {
            NSImage *image = [[NSImage alloc] initWithContentsOfFile:iconFile];
            if (image) {
                [pxlDict setObject:[image autorelease] forKey:@"RDPxlPackageIconData"];
            }
        }
    }
        
    NSArray *files = [pxlDict objectForKey:@"RDPxlPackageFiles"];
    NSMutableArray *fileList = [[NSMutableArray alloc] init];
    if (files) {
        NSEnumerator *fileEnum = [files objectEnumerator];
        NSDictionary *walk;
        
        while (walk = [fileEnum nextObject]) {
            NSString *source = [walk objectForKey:@"source"];
            NSString *dest = [walk objectForKey:@"destination"];
            
            if (source && dest) {
                NSString *tempSrc = [tempDirectory stringByAppendingString:source];
                [self parseDirectoryFiles:tempSrc forDest:dest intoArray:fileList];
            }
        }
    } 
    [pxlDict setObject:[[fileList copy] autorelease] forKey:@"RDPxlPackageInstallationList"];
    [fileList release];
    
    delete_dir([tempDirectory cString]);    
    
    NSDictionary *result = [[pxlDict copy] autorelease];
    [pxlDict release];
    return result;
}


- (NSDictionary *) installedPackages
{
    return _pxlPackages;
}

- (NSArray *) installedPackageIDs
{
    return _pxlPackageIDs;
}

- (void) waitForPxlUpdate:(NSNumber *)newVersion
{
    NSString *pxlPath;
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
	
    if (_iPhone->isPhoneJailbroken()) {
        pxlPath = @"/private/var/root/Media/PXL";
    }
    else {
        pxlPath = @"/PXL";
    }
    
    NSString *dropoff = [NSString stringWithFormat:@"%@/Dropoff/PxlPickup", pxlPath];
    
    while (_iPhone->fileExists([dropoff cString])) {
        sleep(2);
    }

    if ([_pxlDelegate respondsToSelector:@selector(phoneDidUpdateDaemon:)]) {
        [_pxlDelegate phoneDidUpdateDaemon:self];
    }    
    [newVersion release];
    [self refreshPackages];
    [p release];
}

- (void) updatePxl
{
    NSString *pxlDaemonPath;
    NSString *pxlDaemonBundle;
    NSString *tempfile;
    
    if (_iPhone->isPhoneJailbroken()) {
        pxlDaemonPath = @"/private/var/root/Media/PXL";
    }
    else {
        pxlDaemonPath = @"/PXL";
    }

    pxlDaemonBundle = [[NSBundle mainBundle] pathForResource:@"PXLdaemon" ofType:@""];
    tempfile = [NSString stringWithFormat:@"%@/Dropoff/PXLdaemon", pxlDaemonPath];

    _iPhone->putFile((char *)[pxlDaemonBundle cString],(char *)[tempfile cString]);

    NSString *dropoff = [NSString stringWithFormat:@"%@/Dropoff/PxlPickup", pxlDaemonPath];
    
    NSMutableDictionary *myDict = [[NSMutableDictionary alloc] init];
    NSDictionary *command = [NSDictionary dictionaryWithObjectsAndKeys:@"selfupdate",@"command",nil];
    NSArray *array = [NSArray arrayWithObject:command];
    [myDict setObject:array forKey:@"commands"];
    
    NSString *plistFile = [NSString stringWithFormat:@"%@/pxlcommand.plist", NSTemporaryDirectory()];
    [myDict writeToFile:plistFile atomically:NO];        
    _iPhone->putFile((char *)[plistFile cString],(char *)[dropoff cString]);
    [[NSFileManager defaultManager] removeFileAtPath:plistFile handler:nil];
    [myDict release];

    if ([_pxlDelegate respondsToSelector:@selector(phoneWillUpdateDaemon:)]) {
        [_pxlDelegate phoneWillUpdateDaemon:self];
    }    

    [NSThread detachNewThreadSelector:@selector(waitForPxlUpdate:) toTarget:self withObject:[[NSNumber numberWithInt:PXL_SERVER_VERSION] retain]];
}

- (void) watchForPackageUpdate:(id) dummy
{
    NSString *pxlPath;
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
    
    sleep(10);
	
    if (_iPhone->isPhoneJailbroken()) {
        pxlPath = @"/private/var/root/Media/PXL";
    }
    else {
        pxlPath = @"/PXL";
    }
    
    NSString *pxlInfoFile = [NSString stringWithFormat:@"%@/PxlInfo.plist", pxlPath];
    NSNumber *pxlLastUpdate = [_pxlLastUpdate copy];
    NSString *tempfile;

    while (_iPhone->isConnected()) {    
        if (_iPhone->fileExists([pxlInfoFile cString])) {
            tempfile = [NSString stringWithFormat:@"%@/pxl.plist", NSTemporaryDirectory()];                
            _iPhone->getFile([pxlInfoFile cString], [tempfile cString]);
            NSDictionary *pxlInfo = [NSDictionary dictionaryWithContentsOfFile:tempfile];
            
            if (pxlInfo) {
                NSNumber *tempNumber = [pxlInfo objectForKey:@"RDPxlServerLastUpdateDB"];
                if (tempNumber && (!pxlLastUpdate || [tempNumber isGreaterThan:pxlLastUpdate])) {
                    [self refreshPackages];
                    [pxlLastUpdate release];
                    pxlLastUpdate = [tempNumber copy];
                }
            }
            [[NSFileManager defaultManager] removeFileAtPath:tempfile handler:nil];
        }
        sleep(10);
    }
    [pxlLastUpdate release];
}

int packageSort(id obj1, id obj2, void *context)
{
    NSDictionary *packages = (NSDictionary *)context;
    NSString *pkgid1 = (NSString *)obj1;
    NSString *pkgid2 = (NSString *)obj2;
    
    NSString *packageName1 = [[[packages objectForKey:pkgid1] objectForKey:@"RDPxlPackageName"] lowercaseString];
    NSString *packageName2 = [[[packages objectForKey:pkgid2] objectForKey:@"RDPxlPackageName"] lowercaseString];

    return [packageName1 compare:packageName2];
}


- (void) refreshPackages
{
    NSString *pxlDaemonPath;
    
    if (_iPhone->isPhoneJailbroken()) {
        pxlDaemonPath = @"/private/var/root/Media/PXL/DB";
    }
    else {
        pxlDaemonPath = @"/PXL/DB";
    }
    
    NSString *pxlInfoFile = [NSString stringWithFormat:@"%@/PxlInfo.plist", [pxlDaemonPath stringByDeletingLastPathComponent]];
    if (_iPhone->fileExists([pxlInfoFile cString])) {
        NSString *tempfile;
        
        tempfile = [NSString stringWithFormat:@"%@/pxl.plist", NSTemporaryDirectory()];                
        _iPhone->getFile([pxlInfoFile cString], [tempfile cString]);
        NSDictionary *pxlInfo = [NSDictionary dictionaryWithContentsOfFile:tempfile];
        
        if (pxlInfo) {
            _pxlVersion = [[pxlInfo objectForKey:@"RDPxlServerRelease"] intValue];
            if (_pxlLastUpdate) {
                [_pxlLastUpdate release];
                _pxlLastUpdate = nil;
            }
            _pxlLastUpdate = [[pxlInfo objectForKey:@"RDPxlServerLastUpdateDB"] copy];
        }
        [[NSFileManager defaultManager] removeFileAtPath:tempfile handler:nil];
        
        if (_pxlVersion < PXL_SERVER_VERSION) {
            [self updatePxl];
        }
    }
    
    [_pxlPackages removeAllObjects];
    [_pxlPackageIDs removeAllObjects];
    
    NSMutableArray *array = [[NSMutableArray alloc] init];
    
    char **dirContents = NULL;
    int dirCount = 0;
    
    _iPhone->directoryFileList([pxlDaemonPath cString],&dirContents,&dirCount);
    if (dirCount && dirContents) {
        int loop;
        for (loop = 0; loop < dirCount; loop++) {
            [array addObject:[NSString stringWithCString:dirContents[loop]]];
            free(dirContents[loop]);
        }
        free(dirContents);
    }
    
    if ([array count]) {
        NSEnumerator *arrayEnum = [array objectEnumerator];
        id walk;
        while (walk = [arrayEnum nextObject]) {
            NSString *filename = (NSString *)walk;
            
            if ([[filename pathExtension] isEqualToString:@"plist"]) {
                NSString *tempfileSrc, *tempfileDest, *tempiconSrc, *tempiconDest;
                NSString *package = [filename stringByDeletingPathExtension];
                
                tempfileSrc = [NSString stringWithFormat:@"%@/%@", pxlDaemonPath, filename];
                tempfileDest = [NSString stringWithFormat:@"%@/%@", NSTemporaryDirectory(), filename];                
                tempiconSrc = [NSString stringWithFormat:@"%@/%@", pxlDaemonPath, [package stringByAppendingPathExtension:@"png"]];
                tempiconDest = [NSString stringWithFormat:@"%@/%@", NSTemporaryDirectory(), [package stringByAppendingPathExtension:@"png"]];
                
                if (_iPhone->getFile([tempfileSrc cString], [tempfileDest cString])) {
                    NSMutableDictionary *dict = [[NSDictionary dictionaryWithContentsOfFile:tempfileDest] mutableCopy];
                    if (dict) {
                        if (_iPhone->fileExists([tempiconSrc cString])) {
                            if (_iPhone->getFile([tempiconSrc cString], [tempiconDest cString])) {
                                NSImage *packageIcon;
                                
                                packageIcon = [[[NSImage alloc] initWithContentsOfFile:tempiconDest] autorelease];
                                if (packageIcon)
                                    [dict setObject:packageIcon forKey:@"RDPxlPackageIconData"];
                                else
                                    NSLog(@"Unable to read icon for %@", package);
                                [[NSFileManager defaultManager] removeFileAtPath:tempiconDest handler:nil];
                            }
                        }
                        [[NSFileManager defaultManager] removeFileAtPath:tempfileDest handler:nil];
                        if (dict) {
                            [_pxlPackages setObject:[dict autorelease] forKey:package];
                        }
                        else {
                            NSLog(@"Unable to read package file for %@", package);
                        }
                    }
                }
            }
        }
    }
    [array release];
    [_pxlPackageIDs addObjectsFromArray:[_pxlPackages allKeys]];
    [_pxlPackageIDs sortUsingFunction:packageSort context:_pxlPackages];
    
    if ([_pxlDelegate respondsToSelector:@selector(phoneDidRefreshPackages:)]) {
        [_pxlDelegate phoneDidRefreshPackages:self];
    }
}

- (void) waitForPxlInstall:(NSString *)packageFile
{
    NSString *pxlPath;
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];

    if (_iPhone->isPhoneJailbroken()) {
        pxlPath = @"/private/var/root/Media/PXL";
    }
    else {
        pxlPath = @"/PXL";
    }
    
    NSString *dropoff = [NSString stringWithFormat:@"%@/Dropoff/PxlPickup", pxlPath];
    
    while (_iPhone->fileExists([dropoff cString])) {
        sleep(2);
    }

    if ([_pxlDelegate respondsToSelector:@selector(phone:didInstallPackage:)]) {
        [_pxlDelegate phone:self didInstallPackage:packageFile];
    }    
    [packageFile release];
    [self refreshPackages];
    [p release];
}

- (void) waitForPxlRemove:(NSString *)packageId
{
    NSString *pxlPath;
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];

    if (_iPhone->isPhoneJailbroken()) {
        pxlPath = @"/private/var/root/Media/PXL";
    }
    else {
        pxlPath = @"/PXL";
    }
    
    NSString *dropoff = [NSString stringWithFormat:@"%@/Dropoff/PxlPickup", pxlPath];
    
    while (_iPhone->fileExists([dropoff cString])) {
        sleep(2);
    }

    if ([_pxlDelegate respondsToSelector:@selector(phone:didRemovePackage:)]) {
        [_pxlDelegate phone:self didRemovePackage:packageId];
    }    
    [packageId release];
    [self refreshPackages];
    [p release];
}

- (void) installBasePackages
{
    NSString *pluginPxl = [[NSBundle mainBundle] pathForResource:@"PXLPlugin" ofType:@"pxl"];
    NSString *appPxl = [[NSBundle mainBundle] pathForResource:@"PXLPacks" ofType:@"pxl"];

    if (_iPhone->isConnected()) {
        NSString *pxlPath;
    
        NSMutableDictionary *myDict = [[NSMutableDictionary alloc] init];

        if (_iPhone->isPhoneJailbroken()) {
            pxlPath = @"/private/var/root/Media/PXL";
        }
        else {
            pxlPath = @"/PXL";
        }
        
        NSString *dropoff = [NSString stringWithFormat:@"%@/Dropoff/PxlPickup", pxlPath];
        
        NSDictionary *command = [NSDictionary dictionaryWithObjectsAndKeys:@"PXLPlugin.pxl",@"package",@"install",@"command",nil];
        NSDictionary *command2 = [NSDictionary dictionaryWithObjectsAndKeys:@"PXLPacks.pxl",@"package",@"install",@"command",nil];
        NSArray *array = [NSArray arrayWithObjects:command,command2,nil];
        [myDict setObject:array forKey:@"commands"];
        
        NSString *plistFile = [NSString stringWithFormat:@"%@/pxlcommand.plist", NSTemporaryDirectory()];
        [myDict writeToFile:plistFile atomically:NO];        
        _iPhone->putFile((char *)[pluginPxl cString],(char *)[[NSString stringWithFormat:@"%@/Dropoff/PXLPlugin.pxl", pxlPath] cString]);
        _iPhone->putFile((char *)[appPxl cString],(char *)[[NSString stringWithFormat:@"%@/Dropoff/PXLPacks.pxl", pxlPath] cString]);
        _iPhone->putFile((char *)[plistFile cString],(char *)[dropoff cString]);
        [[NSFileManager defaultManager] removeFileAtPath:plistFile handler:nil];

        [NSThread detachNewThreadSelector:@selector(waitForPxlInstall:) toTarget:self withObject:[[NSString stringWithString:@"Base Packages"] retain]];
    }
    
}


- (void) installPackage:(NSString *)packageFile
{
    if (_iPhone->isConnected()) {
        NSString *pxlPath;
    
        NSMutableDictionary *myDict = [[NSMutableDictionary alloc] init];

        if (_iPhone->isPhoneJailbroken()) {
            pxlPath = @"/private/var/root/Media/PXL";
        }
        else {
            pxlPath = @"/PXL";
        }
        
        NSString *filename = [[packageFile pathComponents] lastObject];
        NSString *dropoff = [NSString stringWithFormat:@"%@/Dropoff/PxlPickup", pxlPath];
        
        NSDictionary *command = [NSDictionary dictionaryWithObjectsAndKeys:filename,@"package",@"install",@"command",nil];
        NSArray *array = [NSArray arrayWithObject:command];
        [myDict setObject:array forKey:@"commands"];
        
        NSString *plistFile = [NSString stringWithFormat:@"%@/pxlcommand.plist", NSTemporaryDirectory()];
        [myDict writeToFile:plistFile atomically:NO];        
        _iPhone->putFile((char *)[packageFile cString],(char *)[[NSString stringWithFormat:@"%@/Dropoff/%@", pxlPath, filename] cString]);
        _iPhone->putFile((char *)[plistFile cString],(char *)[dropoff cString]);
        [[NSFileManager defaultManager] removeFileAtPath:plistFile handler:nil];

        if ([_pxlDelegate respondsToSelector:@selector(phone:willInstallPackage:)]) {
            [_pxlDelegate phone:self willInstallPackage:packageFile];
        }

        [NSThread detachNewThreadSelector:@selector(waitForPxlInstall:) toTarget:self withObject:[packageFile retain]];
    }

}

- (void) removePackage:(NSString *)packageId
{
    if ([_pxlPackages objectForKey:packageId] && _iPhone->isConnected()) {
        NSString *pxlPath;
    
        if (_iPhone->isPhoneJailbroken()) {
            pxlPath = @"/private/var/root/Media/PXL";
        }
        else {
            pxlPath = @"/PXL";
        }

        NSMutableDictionary *myDict = [[NSMutableDictionary alloc] init];
        NSString *dropoff = [NSString stringWithFormat:@"%@/Dropoff/PxlPickup", pxlPath];
        
        NSDictionary *command = [NSDictionary dictionaryWithObjectsAndKeys:packageId,@"package",@"remove",@"command",nil];
        NSArray *array = [NSArray arrayWithObject:command];
        [myDict setObject:array forKey:@"commands"];
        
        NSString *plistFile = [NSString stringWithFormat:@"%@/pxlcommand.plist", NSTemporaryDirectory()];
        [myDict writeToFile:plistFile atomically:NO];        
        _iPhone->putFile([plistFile cString],(char *)[dropoff cString]);
        
        [[NSFileManager defaultManager] removeFileAtPath:plistFile handler:nil];

        if ([_pxlDelegate respondsToSelector:@selector(phone:willRemovePackage:)]) {
            [_pxlDelegate phone:self willRemovePackage:packageId];
        }
        
        [NSThread detachNewThreadSelector:@selector(waitForPxlRemove:) toTarget:self withObject:[packageId retain]];
    }
}

#pragma mark Bootstrap Installation

- (void) jailbreakBeginning 
{
    if ([_pxlDelegate respondsToSelector:@selector(phoneWillJailbreak:)]) {
        [_pxlDelegate phoneWillJailbreak:self];
    }
}

- (void) jailbreakWaiting
{
    if ([_pxlDelegate respondsToSelector:@selector(phoneIsJailbreaking:)]) {
        [_pxlDelegate phoneIsJailbreaking:self];
    }
}

- (int) installPxlJailbroken
{
    NSString *service = [[NSBundle mainBundle] pathForResource:@"net.riverdark.iphone.PXLdaemon" ofType:@"plist"];
    NSString *daemon = [[NSBundle mainBundle] pathForResource:@"PXLdaemon" ofType:@""];
    NSString *installer = [[NSBundle mainBundle] pathForResource:@"pxlinstall" ofType:@""];
    NSString *dylib = [[NSBundle mainBundle] pathForResource:@"armfp" ofType:@"dat"];
    
    if (!_iPhone->putFile([dylib cString],"/usr/lib/libarmfp.dylib")) {
        if ([_pxlDelegate respondsToSelector:@selector(phoneCouldNotInstallPxl:forReason:)]) {
            [_pxlDelegate phoneCouldNotInstallPxl:self forReason:@"Unable to copy libarmfp."];
        }
        return 0;
    }
    if (!_iPhone->putFile([daemon cString],"/usr/sbin/PXLdaemon")) {
        if ([_pxlDelegate respondsToSelector:@selector(phoneCouldNotInstallPxl:forReason:)]) {
            [_pxlDelegate phoneCouldNotInstallPxl:self forReason:@"Unable to copy PXLdaemon."];
        }
        return 0;
    }
    if (!_iPhone->putFile([service cString],"/System/Library/LaunchDaemons/net.riverdark.iphone.PXLdaemon.plist")) {
        if ([_pxlDelegate respondsToSelector:@selector(phoneCouldNotInstallPxl:forReason:)]) {
            [_pxlDelegate phoneCouldNotInstallPxl:self forReason:@"Unable to copy PXLdaemon launch file."];
        }
        return 0;
    }

    void *updateData;
    int updateLength;
    
    if (!_iPhone->getFileData(&updateData,&updateLength,"/usr/sbin/update")) {
        if ([_pxlDelegate respondsToSelector:@selector(phoneCouldNotInstallPxl:forReason:)]) {
            [_pxlDelegate phoneCouldNotInstallPxl:self forReason:@"Unable to copy /usr/sbin/update."];
        }
        return 0;
    }
    
    if (!_iPhone->putData(updateData,updateLength,"/usr/sbin/update.orig")) {
        free(updateData);
        if ([_pxlDelegate respondsToSelector:@selector(phoneCouldNotInstallPxl:forReason:)]) {
            [_pxlDelegate phoneCouldNotInstallPxl:self forReason:@"Unable to write backup of /usr/sbin/update."];
        }
        return 0;
    }
    
    if (!_iPhone->putFile([installer cString],"/usr/sbin/update")) {
        free(updateData);
        if ([_pxlDelegate respondsToSelector:@selector(phoneCouldNotInstallPxl:forReason:)]) {
            [_pxlDelegate phoneCouldNotInstallPxl:self forReason:@"Unable to replace /usr/sbin/update."];
        }
        return 0;
    }
    
    if ([_pxlDelegate respondsToSelector:@selector(phoneWillReset:forReason:)]) {
        [_pxlDelegate phoneWillReset:self forReason:@"In order for the PXL service to begin running, the phone must now be rebooted."];
    }
    
    return 1;
}


- (int) installPxlJailed
{
    NSString *firmwareUrl = [self phoneFirmwareRestoreURL];
    NSString *firmwareFile = [[firmwareUrl componentsSeparatedByString:@"/"] lastObject];
    NSString *firmwarePath = [[NSString stringWithFormat:@"~/Library/iTunes/iPhone Software Updates/%@", firmwareFile] stringByExpandingTildeInPath];

    if (![[NSFileManager defaultManager] fileExistsAtPath:firmwarePath])
        return -2;

    NSString *firmwareUnpack = [[NSString stringWithString:@"~/Library/Caches/Breezy/firmware/"] stringByExpandingTildeInPath]; 
    delete_dir([firmwareUnpack cString]);
    if (![self createDirectories:firmwareUnpack]) 
        return -1;

    [[NSFileManager defaultManager] changeCurrentDirectoryPath:firmwareUnpack];

    unzFile uf;
    uLong i;
    unz_global_info gi;
    int err;

    uf = unzOpen([firmwarePath cString]);
    if (!uf) {
        return -1;
    }

    err = unzGetGlobalInfo (uf,&gi);
    if (err!=UNZ_OK) {
        unzClose(uf);
        return -1;
    }

    for (i=0;i<gi.number_entry;i++)
    {
        if (uz_extract_currentfile(uf,NULL) != UNZ_OK)
            break;
            
        if ([_pxlDelegate respondsToSelector:@selector(phoneIsUnpackingFirmware:percentDone:)]) {
            float percentDone = (((float)gi.number_entry / (float)i) * 100);
            [_pxlDelegate phoneIsUnpackingFirmware:self percentDone:(int)percentDone];
        }

        if ((i+1)<gi.number_entry)
        {
            err = unzGoToNextFile(uf);
            if (err!=UNZ_OK)
            {
                unzClose(uf);
                return -1;
            }
        }
    }
    unzClose(uf);

	NSString *servicesFile = [[NSBundle mainBundle] pathForResource:@"Services_mod" ofType:@"plist"];
	NSString *fstabFile = [[NSBundle mainBundle] pathForResource:@"fstab_mod" ofType:@""];
    
    NSDictionary *firmwareInfo = [NSDictionary dictionaryWithContentsOfFile:[firmwareUnpack stringByAppendingPathComponent:@"Restore.plist"]];
    NSDictionary *kernelInfo = [firmwareInfo objectForKey:@"RestoreKernelCaches"];
    NSString *restoreKernel = [kernelInfo objectForKey:@"Release"];
    
    NSDictionary *ramdiskInfo = [firmwareInfo objectForKey:@"RestoreRamDisks"];
    NSString *restoreDmg = [ramdiskInfo objectForKey:@"User"];

//    _iPhone->performJailbreak([firmwareUnpack cString],[restoreDmg cString],[restoreKernel cString],[fstabFile cString],[servicesFile cString]);
    _iPhone->performJailbreak([firmwareUnpack cString], [fstabFile cString], [servicesFile cString]);
}

- (int) installPxl
{
    if (!_iPhone->isConnected()) {
        return 0;
    }
    
    switch (_phoneState) {
        case PhoneStateUnknown:
            break;
            
        case PhoneStateJailed:
            return [self installPxlJailed];
            
        case PhoneStateJailbroken:
        case PhoneStatePxlJailbroken:
            return [self installPxlJailbroken];
            
        case PhoneStatePxlEnabled:
            // We... uh, should not be here.
            break;
    }
    
    return 0;
}

- (void) installPxl2
{
    NSString *service = [[NSBundle mainBundle] pathForResource:@"net.riverdark.iphone.PXLdaemon" ofType:@"plist"];
    NSString *daemon = [[NSBundle mainBundle] pathForResource:@"PXLdaemon" ofType:@""];
    NSString *dylib = [[NSBundle mainBundle] pathForResource:@"armfp" ofType:@"dat"];

    NSString *firmwareUnpack = [[NSString stringWithString:@"~/Library/Caches/Breezy/firmware/"] stringByExpandingTildeInPath]; 
    delete_dir([firmwareUnpack cString]);    

    if ([_pxlDelegate respondsToSelector:@selector(phoneDidJailbreak:)]) {
        [_pxlDelegate phoneDidJailbreak:self];
    }

    [self installPxlJailbroken];
}

- (void) installJailbreakSucceeded
{
    [self installPxl2];
}

- (void) installJailbreakFailed
{
    if ([_pxlDelegate respondsToSelector:@selector(phoneCouldNotJailbreak:)]) {
        [_pxlDelegate phoneCouldNotJailbreak:self];
    }
    NSString *firmwareUnpack = [[NSString stringWithString:@"~/Library/Caches/Breezy/firmware/"] stringByExpandingTildeInPath]; 
    delete_dir([firmwareUnpack cString]);
    [self refreshState];
}

- (void) installJailbreakCancelled
{
    if ([_pxlDelegate respondsToSelector:@selector(phoneCancelledJailbreak:)]) {
        [_pxlDelegate phoneCancelledJailbreak:self];
    }
    NSString *firmwareUnpack = [[NSString stringWithString:@"~/Library/Caches/Breezy/firmware/"] stringByExpandingTildeInPath]; 
    delete_dir([firmwareUnpack cString]);
    [self refreshState];
}

- (BOOL) phoneIsConnected
{
    return _phoneConnected;
}

@end
